package jm.easyconfig;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.Arrays;
import java.util.Base64;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.core.io.ClassPathResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.AbstractHttpMessageConverter;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.DigestUtils;

public class CfgHelper {
	static final Logger logger = LoggerFactory.getLogger("jm.easyconfig");

	static String hash(String txt) {
		return DigestUtils.md5DigestAsHex(txt.getBytes(StandardCharsets.UTF_8));
	}

	static String base64(String text) {
		return Base64.getEncoder().encodeToString(text.getBytes(StandardCharsets.UTF_8));
	}

	static String debase64(String text64code) {
		return new String(Base64.getDecoder().decode(text64code), StandardCharsets.UTF_8);
	}

	static String readTextFile(String classPath) {
		String content = "";

		ClassPathResource resource = new ClassPathResource(classPath);

		try (BufferedReader reader = new BufferedReader(
				new InputStreamReader(resource.getInputStream(), StandardCharsets.UTF_8));) {

			content = reader.lines().collect(Collectors.joining("\n"));

		} catch (Exception e) {
			e.printStackTrace();
		}

		return content;
	}

	@SuppressWarnings("unchecked")
	static public Map<String, Object> jsonRead(String jsonText) {
		try {
			return (Map<String, Object>) jsonRead(jsonText, Map.class);
		} catch (Throwable e) {
			e.printStackTrace();
		}
		return null;
	}

//	static ObjectMapper objectMapper;
//
//	static ObjectMapper objectMapper() {
//		if (objectMapper == null) {
//			try {
//				ObjectMapper m = new com.fasterxml.jackson.databind.ObjectMapper();
//				m.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
//				m.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
//				objectMapper = m;
//			} catch (Throwable e) {
//				e.printStackTrace();
//				CfgHelper.logger.error("fatal error when to create json converter.{e}", e);
//			}
//		}
//
//		return objectMapper;
//	}
//
//	@SuppressWarnings("unchecked")
//	static public <T> T jsonRead(String jsonText, Class<T> clazz) {
//		try {
//			return objectMapper().readValue(jsonText, clazz);
//		} catch (Throwable e) {
//			throw new RuntimeException(e);
//		}
//	}
//
//	static public <T> String jsonWrite(T bean) {
//		try {
//			return objectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(bean);
//		} catch (Throwable e) {
//			throw new RuntimeException(e);
//		}
//	}

	static AbstractHttpMessageConverter<?> converter = null;

	static List<String> supportedJsonConverter = Arrays
			.asList(new String[] { "org.springframework.http.converter.json.MappingJackson2HttpMessageConverter",
					"com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter",
					"org.springframework.http.converter.json.GsonHttpMessageConverter",
					"org.springframework.http.converter.json.JsonbHttpMessageConverter",
					"org.springframework.http.converter.json.KotlinSerializationJsonHttpMessageConverter" });

	@SuppressWarnings("unchecked")
	private static <T> AbstractHttpMessageConverter<T> converter() {
		if (converter != null)
			return (AbstractHttpMessageConverter<T>) converter;

		try {
			AbstractHttpMessageConverter<T> cvt;
			ApplicationContext ctx = CfgContextEnvironmentAware.applicationContext;
			String[] names = ctx != null ? ctx.getBeanNamesForType(AbstractHttpMessageConverter.class, false, false)
					: new String[] {};

			for (String beanName : names) {
				cvt = (AbstractHttpMessageConverter<T>) ctx.getBean(beanName);
				if (supportedJsonConverter.contains(cvt.getClass().getName())) {
					converter = (AbstractHttpMessageConverter<T>) cvt;
					return (AbstractHttpMessageConverter<T>) converter;
				}
			}

			if (ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", CfgHelper.class.getClassLoader())
					&& ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator",
							CfgHelper.class.getClassLoader())) {

				converter = (AbstractHttpMessageConverter<T>) ClassUtils
						.forName("org.springframework.http.converter.json.MappingJackson2HttpMessageConverter",
								CfgHelper.class.getClassLoader())
						.newInstance();

			} else if (ClassUtils.isPresent("com.alibaba.fastjson.JSON", CfgHelper.class.getClassLoader())) {
				converter = (AbstractHttpMessageConverter<?>) ClassUtils
						.forName("com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter",
								CfgHelper.class.getClassLoader())
						.newInstance();

			} else if (ClassUtils.isPresent("com.google.gson.Gson", CfgHelper.class.getClassLoader())) {
				converter = (AbstractHttpMessageConverter<T>) ClassUtils
						.forName("org.springframework.http.converter.json.GsonHttpMessageConverter",
								CfgHelper.class.getClassLoader())
						.newInstance();

			} else if (ClassUtils.isPresent("javax.json.bind.Jsonb", CfgHelper.class.getClassLoader())) {
				converter = (AbstractHttpMessageConverter<T>) ClassUtils
						.forName("org.springframework.http.converter.json.JsonbHttpMessageConverter",
								CfgHelper.class.getClassLoader())
						.newInstance();

			} else if (ClassUtils.isPresent("kotlinx.serialization.json.Json", CfgHelper.class.getClassLoader())) {
				converter = (AbstractHttpMessageConverter<T>) ClassUtils
						.forName("org.springframework.http.converter.json.KotlinSerializationJsonHttpMessageConverter",
								CfgHelper.class.getClassLoader())
						.newInstance();

			} else {
				CfgHelper.logger.error("fatal error,no json converter is found.");
			}

		} catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) {
			e.printStackTrace();
			CfgHelper.logger.error("fatal error when to create json converter.{e}", e);
		}

		return (AbstractHttpMessageConverter<T>) converter;
	}

	/*
	 * in order to remove the dependency on spring-test-***.jar copy its
	 * MockHttpInputMessage and MockHttpOutputMessage here
	 */
	static class MockHttpInputMessage implements HttpInputMessage {

		private final HttpHeaders headers = new HttpHeaders();

		private final InputStream body;

		public MockHttpInputMessage(byte[] content) {
			Assert.notNull(content, "Byte array must not be null");
			this.body = new ByteArrayInputStream(content);
		}

		public MockHttpInputMessage(InputStream body) {
			Assert.notNull(body, "InputStream must not be null");
			this.body = body;
		}

		@Override
		public HttpHeaders getHeaders() {
			return this.headers;
		}

		@Override
		public InputStream getBody() throws IOException {
			return this.body;
		}

	}

	static class MockHttpOutputMessage implements HttpOutputMessage {

		private static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;

		private final HttpHeaders headers = new HttpHeaders();

		private final ByteArrayOutputStream body = new ByteArrayOutputStream(1024);

		/**
		 * Return the headers.
		 */
		@Override
		public HttpHeaders getHeaders() {
			return this.headers;
		}

		/**
		 * Return the body content.
		 */
		@Override
		public OutputStream getBody() throws IOException {
			return this.body;
		}

		/**
		 * Return body content as a byte array.
		 */
		public byte[] getBodyAsBytes() {
			return this.body.toByteArray();
		}

		/**
		 * Return the body content interpreted as a UTF-8 string.
		 */
//		public String getBodyAsString() {
//			return getBodyAsString(DEFAULT_CHARSET);
//		}

		/**
		 * Return the body content as a string.
		 * 
		 * @param charset the charset to use to turn the body content to a String
		 */
//		public String getBodyAsString(Charset charset) {
//			return StreamUtils.copyToString(this.body, charset);
//		}

	}

	@SuppressWarnings("unchecked")
	static public <T> T jsonRead(String jsonText, Class<T> clazz) {
		try {
			MockHttpInputMessage inputMessage = new MockHttpInputMessage(jsonText.getBytes(StandardCharsets.UTF_8));

			return (T) converter().read(clazz, inputMessage);

		} catch (Throwable e) {
			CfgHelper.logger.error("error to parse json text of {}", jsonText);
			throw new RuntimeException(e);
		}
	}

	static public <T> String jsonWrite(T bean) {
		if (bean == null)
			return "null";

		try {
			MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();

			converter().write(bean, MediaType.APPLICATION_JSON, outputMessage);

//			return outputMessage.getBodyAsString();
			return outputMessage.body.toString("UTF-8");
		} catch (Throwable e) {
			throw new RuntimeException(e);
		}
	}

	public static String encrypt(String content) {
		return CfgApplication.encrypt(content, SysProps.inst.cipherSalt);
	}

	public static String decrypt(String content) {
		return CfgApplication.decrypt(content, SysProps.inst.cipherSalt);
	}

	static long unixMsOfNow() {
		LocalDateTime ldt = LocalDateTime.now(ZoneOffset.UTC);

		// micro seconds of unix time
		return ldt.toEpochSecond(ZoneOffset.UTC) * 1000;
	}

}